---------------------
-- Robotic Panel   --
---------------------
-- A Cirr Creation --
---------------------
-- Hovering panel that mines the ground for material and builds material harvesters 
-- to be used for panel factories.
-- They can also upgrade themselves with their gathered materials, granting them
-- a higher energy store, higher armour, higher speed and stronger beam weapon.
-- Energy regenerates without external cause provided the panel stays still.
-- Health can be replenished using materials, and energy can act as a shield, although
-- if there's not enough energy to continue running the panel drops to the ground
-- until another panel revives it.

AddCSLuaFile('cl_init.lua')
AddCSLuaFile('shared.lua')
include('shared.lua')

--------------------
-- Initialization --
--------------------

function ENT:Initialize()
  
  self:SetModel("models/XQM/panel1x1.mdl")
  self:SetMaterial("models/Maratix/panel_diffuse")
  
  -- Set up physics
  local mins = Vector(-10, -10, -0.75)
  local maxs = Vector(10, 10, 0.75)
  
  self:PhysicsInitBox(mins, maxs)
  self:SetCollisionBounds(mins, maxs)
  
  self:SetSolid(SOLID_VPHYSICS)
  self:SetMoveType(MOVETYPE_VPHYSICS)
  
  self.Phys = self:GetPhysicsObject()
  if self.Phys:IsValid() then
    self.Phys:SetMass(40)
    self.Phys:Wake()
  end
  
  -- Set variables specific to the individual 
  
  self:SetName("Robotic Panel")
  
  self.IsPanel = true
  self.Level = self.Level or 0
  
  -- Life variables
  
  self.MaxHealth = self.MaxHealth or Panels.HEALTH_MAX_BASE * Panels.LEVEL_MULTIPLIER^self.Level
  self.MaxEnergy = self.MaxEnergy or Panels.ENERGY_MAX_BASE * Panels.LEVEL_MULTIPLIER^self.Level
  self.CurrHealth = self.CurrHealth or self.MaxHealth
  self.Energy = self.Energy or self.MaxEnergy
  
  self.IsAlive = self.IsAlive or true
  self.Dormant = self.Dormant or false -- If a panel runs out of energy, it becomes dormant, but doesn't die.
  
  -- Materials and damage variables
  
  self.Materials = self.Materials or 0
  self.BeamDamage = self.BeamDamage or Panels.BEAM_DAMAGE_BASE * Panels.LEVEL_MULTIPLIER^self.Level
  
  -- Targetting variables
  
  self.Target = self.Target or NULL -- Multipurpose variable used to define the target of whatever behaviour the panel is performing.
  self.TargetDistance = self.TargetDistance or 0 -- Distance to approach from the target
  self.NextDecideTime = self.NextDecideTime or CurTime()
  
  self.ColorPercent = self.ColorPercent or 0
  self.ColorTarg = self.ColorTarg or Panels.IDLE_COLOR
  self.OldColor = self.OldColor or Panels.IDLE_COLOR
  
  self.HoverPhase = self.HoverPhase or 0
  
  self.MovementFunction = self.MovementFunction or Panels.Movement.Hover
  self.CurrentAction = self.CurrentAction or NULL
  
  self:StartMotionController()
  
  self:EmitSound(Panels.LiveSound, 100, math.random(50, 150)) -- DANGER WILL ROBINSON DANGER
  
end

-- Spawn the entity when selected from the menu
function ENT:SpawnFunction(ply, tr)

  if (!tr.Hit) then -- Don't spawn this unless there's somewhere to spawn it
    return 
  end
  
  local spawnPos = tr.HitPos + tr.HitNormal * 30 -- Shove it 30 units up
  local ent = ents.Create("sent_panel")
  ent:SetPos(spawnPos)
  ent:Spawn()
  ent:Activate()
  
  return ent

end

------------------------
-- Decision Functions --
------------------------

-- The main decision function
function ENT:Think()
  
  -- Always decide if this behaviour we're following is the best
  if(!self.Dormant && (CurTime() > self.NextDecideTime || self.CurrentAction == NULL)) then
    
    self.CurrentAction, self.MovementFunction = Panels.DecideBehaviour(self)
    self.NextDecideTime = CurTime() + 5
    
  end
  
  -- Act based on what behaviour we're doing! :D
  if(self.CurrentAction != NULL && self.Target:IsValid()) then
  
    self:CurrentAction(self.Target)
  
  end
  
  self.Energy = self.Energy - (Panels.LIFE_ENERGY_COST * 0.2)
  if(self.Energy < 0 && !self.Dormant) then -- no more cheating limbo!
  
    self.Energy = 0
    self.Dormant = true -- Fall to the ground like the unpowered panel we've now become
    self.CurrentAction = NULL
    self:SetNewColor(Panels.IDLE_COLOR)
    self:SetNetworkedBool("drawBeam", false)
    self:EmitSound(Panels.DecisionSounds[math.random(1, #Panels.DecisionSounds)], 100, math.random(10, 70))
    
  end
  
  
  -- Fade colours
  if(self.ColorPercent < 1) then
    self.ColorPercent = self.ColorPercent + 0.1
    self:ColorFade()
  end
  
  -- Adjust this variable to give the nice bobbing effect
  self.HoverPhase = self.HoverPhase + 0.25
  
  -- Set the next think time (0.2 seconds from now)
  self:NextThink(CurTime() + 0.2)
  return true
  
end

-- Be damaged when struck by fast-moving objects
function ENT:PhysicsCollide(data, phys)
  
  if(data.Speed > 1000) then
    
    self:TakeDamage(0.2 * (data.Speed - 1000), data.HitEntity, data.HitEntity)
    
  end
  
end

-- Actions to take when damaged
function ENT:OnTakeDamage(dmginfo)
  
  self:TakePhysicsDamage(dmginfo)
  
  if(!self.IsAlive) then
    return
  end
  
  if(self.Energy > 0) then -- We have shields!
    
    local dmg = dmginfo:GetDamage()
    local diff = self.Energy - dmg
    if(diff > 0) then
      
      self.Energy = diff
      local fx = EffectData() -- Make the shield effect
      local hitPos = dmginfo:GetDamagePosition()
      fx:SetOrigin(self:WorldToLocal(hitPos))
      fx:SetScale(math.Clamp(dmginfo:GetDamage(), 5, 50))
      fx:SetEntity(self)
      util.Effect("panel_shields", fx)
      self:EmitSound(Panels.ShieldSounds[math.random(1, #Panels.ShieldSounds)], math.random(80, 120), math.random(80, 120))
      
    else -- We've been knocked out! No energy, no motion.
      
      self.Energy = 0
      self.CurrHealth = self.CurrHealth - dmg
      self.Dormant = true -- Fall to the ground like the unpowered panel we've now become
      self.CurrentAction = NULL
      self:SetNewColor(Panels.IDLE_COLOR)
      self:SetNetworkedBool("drawBeam", false)
      self:EmitSound(Panels.DecisionSounds[math.random(1, #Panels.DecisionSounds)], 100, math.random(10, 70))
      
    end
    
  else -- No shields D:
    
    self.CurrHealth = self.CurrHealth - dmginfo:GetDamage()
    self:EmitSound(Panels.HitSounds[math.random(1, #Panels.HitSounds)], math.random(80, 120), math.random(80, 120))
    if(self.CurrHealth < 0) then
      
      self.IsAlive = false
      self:Die()
      
    end

  end
  
  -- Re-evaluate our priorities
  if(self.Dormant) then
    return -- Dead panels tell no tales! Err, make no decisions.
  end
  
  local healthRatio = self.CurrHealth/self.MaxHealth
  local energyRatio = self.Energy/self.MaxEnergy
  
  if(healthRatio < Panels.HEALTH_FLEE_RATIO || energyRatio < Panels.ENERGY_FLEE_RATIO) then
    
    -- WE'RE GONNA DIEEEE
    self:SetNewColor(Panels.FLEE_COLOR)
    self.CurrentAction = Panels.Flee
    self.MovementFunction = Panels.Movement.Flee
    self:EmitSound(Panels.DecisionSounds[math.random(1, #Panels.DecisionSounds)], 100, math.random(80, 120))
    
  end
  
end

----------------------
-- Helper Functions --
----------------------

function ENT:Die()
  
  -- Deal damage to those around us
  local currPos = self:GetPos()
  local nearbyEnts = ents.FindInSphere(currPos, Panels.DEATH_RADIUS)
  local dist
  local dir
  local value
  
  for k,ent in pairs(nearbyEnts) do
    dist = ent:GetPos():Distance(currPos)
    dir = (ent:GetPos() - currPos):Normalize()
    value = (1 - (dist/Panels.DEATH_RADIUS)) * Panels.DEATH_DAMAGE
    ent:TakeDamage(value, self, self)
    if(ent:IsPlayer() || ent:IsNPC()) then
      ent:SetVelocity(dir * value * 20)
    elseif (ent:GetPhysicsObject():IsValid()) then
      ent:GetPhysicsObject():SetVelocity(dir * value * 20)
    end
  end
  
  -- Now actually explode
  local fx = EffectData()
  fx:SetOrigin(self:GetPos())
  util.Effect("HelicopterMegaBomb", fx, true, true) -- Explosion effect
  self:EmitSound(Panels.DieSound, 100, math.random(80, 120)) -- Explosion sound
  self:Remove() -- Alas, cruel world
  
end

-- Smoothly fade colours
function ENT:ColorFade()
  
  if(self.ColorPercent >= 1) then
    return
  end
  
  self:SetColor(Lerp(self.ColorPercent, self.OldColor.r, self.ColorTarg.r),
                Lerp(self.ColorPercent, self.OldColor.g, self.ColorTarg.g),
                Lerp(self.ColorPercent, self.OldColor.b, self.ColorTarg.b),
                Lerp(self.ColorPercent, self.OldColor.a, self.ColorTarg.a))
  
end

-- Set a new colour to fade to
function ENT:SetNewColor(color)

  self.ColorPercent = 0
  self.OldColor = self.ColorTarg
  self.ColorTarg = color
  self:SetNetworkedVector("color", Vector(color.r, color.g, color.b))

end

------------------------
-- Movement Functions --
------------------------

-- The PhysObj equivalent of Think
function ENT:PhysicsSimulate(phys, delta)

  if(self.Dormant) then
    return SIM_NOTHING
  end
  
  phys:Wake()

  local sc = {}
  sc.secondstoarrive  = 1
  sc.maxspeed         = 4000
  sc.maxspeeddamp     = 500
  sc.maxangular       = 4000
  sc.maxangulardamp   = 500
  sc.dampfactor       = 0.8
  sc.deltatime        = delta
  
  sc.pos, sc.angle = self:MovementFunction(phys, delta)
  
  phys:ComputeShadowControl(sc)
  
  return SIM_NOTHING
  
end